package com.googlecode.curvedmetromaps.core.logic.forcedriven;

import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;

import com.googlecode.curvedmetromaps.core.model.MetroStation;

/**
 * Constraint S9: pushes drawing toward the desired aspect ratio.
 */
public class ForceAspectRatio extends ForceSubsystem {
	// Dynamic factor. Part of the force is influenced by how close the aspect
	// ratio is to the desired factor. If the dynamic factor is 0.9 then the
	// aspect ratio strongly influences the force. If the dynamic factor is 0
	// then the force is only derived from how close stations are to the
	// boundary.
	public ForceWeight dynamic = new ForceWeight("Dynamic factor", 0.80);
	// Factor of frame in which forces operate (e.g. 0.1 means top, left, bottom
	// and right 10%).
	public ForceWeight frameSize = new ForceWeight("Frame size", 0.1);

	ForceAspectRatio(ForceSystem system) {
		super(system);
		baseWeight.value = 500;
	}

	void applyForces(int delta) {
		// Calculate bounding box
		float xmin = Float.POSITIVE_INFINITY;
		float xmax = Float.NEGATIVE_INFINITY;
		float ymin = Float.POSITIVE_INFINITY;
		float ymax = Float.NEGATIVE_INFINITY;
		for (MetroStation m : system.metroMap.stations) {
			Body b = system.stationBodies[m.id];
			Vec2 p = b.getPosition();
			xmin = Math.min(xmin, p.x);
			xmax = Math.max(xmax, p.x);
			ymin = Math.min(ymin, p.y);
			ymax = Math.max(ymax, p.y);
		}

		// Calculate aspect ratio, but prevent division by 0.
		float bbWidth = xmax - xmin;
		float bbHeight = ymax - ymin;

		// Only continue if height is positive.
		if (bbHeight == 0)
			return;
		float bbAspectRatio = bbWidth / bbHeight;

		// Calculate frame, which is a the smallest rectangle that contains the
		// bounding box and has the aspect ratio as defined in MetroMap.
		float frameWidth = bbWidth;
		float frameHeight = bbHeight;
		if (bbAspectRatio < system.metroMap.aspectRatio) {
			// Height of drawing constrains the frame; scale width.
			frameWidth = bbHeight * system.metroMap.aspectRatio;
		} else if (bbAspectRatio > system.metroMap.aspectRatio) {
			// Width of drawing constrains the frame; scale height.
			frameHeight = bbWidth / system.metroMap.aspectRatio;
		}

		float frameL = xmin - 0.5f * (frameWidth - bbWidth);
		float frameB = ymin - 0.5f * (frameHeight - bbHeight);
		float frameR = frameL + frameWidth;
		float frameT = frameB + frameHeight;

		// Calculate width and height of range in which forces operate.
		float bandW = (float) (frameSize.value * frameWidth);
		float bandH = (float) (frameSize.value * frameHeight);

		for (MetroStation m : system.metroMap.stations) {
			Body b = system.stationBodies[m.id];
			Vec2 p = b.getPosition();

			// Calculate resulting force
			Vec2 f = new Vec2();
			if (bbAspectRatio < system.metroMap.aspectRatio) {
				f.y += Math.max(0f, (frameB + bandH) - p.y);
				f.y += Math.min(0f, (frameT - bandH) - p.y);
			} else {
				f.x += Math.max(0f, (frameL + bandW) - p.x);
				f.x += Math.min(0f, (frameR - bandW) - p.x);
			}

			double aspectDiff = Math.abs(bbAspectRatio - system.metroMap.aspectRatio);
			double mag = baseWeight.value * ((1 - dynamic.value) + (dynamic.value * aspectDiff));
			f.mulLocal((float) mag);

			b.applyForceToCenter(f);
		}
	}
}
